
//Thanks to A.R. Collins for writing CanvasStack-- a simple library for layering
//Canvases.

//A 2d array of colored boxes
//As of this instant, a mosaic paints rectangles of a size fixed at 
//initialization to all or some of a canvas.
var Mosaic = function(id,prefs){ //the id of a pre-created div on the page.  The canvas is created inside of this.
    return {
    init:
    function(){
      if(document.getElementById(id).children(id+"_bkg") == null){  //A mosaic defers to the HTML properties of its container.
        this.canvasStack =  new CanvasStack(id),                    //but preferences `xSize` and `ySize` may be passed in manually.
                                                                    //This will c
        this.sprlayer = document.getElementById(this.canvasStack.createLayer() ).getContext('2d');
        this.uilayer  = document.getElementById(this.canvasStack.createLayer() ).getContext('2d');
        this.uilayer2  = document.getElementById(this.canvasStack.createLayer() ).getContext('2d');
      }else{
        this.canvasStack = document.getElementById(id);
        this.sprlayer    = this.canvasStack.children(id+"_ovl_0").getContext('2d');
        this.uilayer     = this.canvasStack.children(id+"_ovl_1").getContext('2d');
        this.uilayer2    = this.canvasStack.children(id+"_ovl_2").getContext('2d');
      }
        this.xSize = document.getElementById(id).children(id+"_bkg").getAttribute('width');
        this.ySize = document.getElementById(id).children(id+"_bkg").getAttribute('height');
        this.xS = Math.floor(this.xSize/this.tileSize);
        this.yS = Math.floor(this.ySize/this.tileSize);
       
      for(var pref in prefs){ // load user preferences.
                              // valid prefs are `xSize`,`ySize`,`tileSize`,`xD`, and `yD`
        this[pref] = prefs[pref];
      }
      this.sprlayer.save();
      this.sprlayer.fillStyle = "rgba("+this.bg.r+","+this.bg.g+","+this.bg.b+","+this.bg.a+")";
      this.sprlayer.fillRect(this.xD,this.yD,this.xSize,this.ySize);
      this.sprlayer.restore();
    this.mosaic = this;
    return this;
    },
    paintCoords: 
    function(x,y,color){
      if(color == undefined)
        color = this.transparent;
      if(!compareColors(this.transparent,color) )
        color = this.bg;
    //colors are stored & passed as Color objects. 
      var ts = this.tileSize;
      this.sprlayer.save();
      this.sprlayer.fillStyle = "rgba("+color.r+","+color.g+","+color.b+","+color.a+")";
      this.sprlayer.fillRect(this.xD+x*ts,this.yD+y*ts,ts,ts);
      this.sprlayer.restore(); 
      return false;
    },

    coordsOf: function(xpos,ypos){
      var x = Math.floor( (xpos - this.xD)/this.tileSize); ///xD and yD are the offsets of the mosaic from the origin in pixels.
      var y = Math.floor( (ypos - this.yD)/this.tileSize); 
      return [x,y]; //WARNING-- this function is NOT scroll-proof.  Going to fix that soon.
    },
    transparent:{r:255,g:255,b:255,a:255},
    tileSize: 1,
    xD:0,
    yD:0,
    bg:{r:0,g:0,b:0,a:255}
  }.init();
}

var Selector = function(mosaic){
  if(mosaic.mosaic == undefined)
    mosaic = document.getElementById(mosaic);
  var rectmanager = RectManager();
  return{
    select:
    function(x0orrect,y0,xs,ys){//draws a box around the selected tile(s), and stores its coordinates.
      if(typeof(x0orrect) != "number"){
        var x0 = x0orrect.xD;
        var y0 = x0orrect.yD;
        var xs = x0orrect.xS;
        var ys = x0orrect.yS;
      }else
      var x0 = x0orrect;
      if(xs===undefined) xs=1;
      if(ys===undefined) ys=1;
      if(mosaic.xOff==undefined) mosaic.xOff=0;
      if(mosaic.yOff==undefined) mosaic.yOff=0;
      if(-1 !=rectmanager.makeRect(x0,y0,xs,ys) )
        this._drawSelect(x0,y0,xs,ys); 
    },

    _drawSelect:
    function(x0,y0,xs,ys){
      var ts = mosaic.mosaic.tileSize;
      mosaic.mosaic.uilayer.save();
      mosaic.mosaic.uilayer.lineWidth = 1;
      mosaic.mosaic.uilayer.strokeRect((x0+mosaic.xOff)*ts+.5+mosaic.mosaic.xD,(y0+mosaic.yOff)*ts+.5+mosaic.mosaic.yD,xs*ts,ys*ts);
      mosaic.mosaic.uilayer.restore();
    },

    boxAt:
    function(x0,y0){
      return rectmanager.rectAt(x0,y0); 
    },

    deselect:
    function(x0,y0){
      rectmanager.removeRectAt(x0,y0);
      this.redraw()
    },
    redraw:
    function(){
      mosaic.mosaic.uilayer.clearRect(0,0,mosaic.mosaic.uilayer.canvas.width,mosaic.mosaic.uilayer.canvas.height);
      for(s in rectmanager.rects){
        if(rectmanager.rects[s] == undefined)
          continue;
        box = rectmanager.rects[s];
        this._drawSelect(box.xD,box.yD,box.xS,box.yS);
      }
      
    },
    deselectAll:
    function(){
      rectmanager.rects = [];
      this.redraw();
    }

  }
}
var _hotpink_drawSelect = function(x0,y0,xs,ys){
  var ts = mosaic.mosaic.tileSize;
  mosaic.mosaic.uilayer.save();
  mosaic.mosaic.uilayer.fillStyle = "rgba(255,0,255,255)";
  mosaic.mosaic.uilayer.fillRect((x0+mosaic.xD)*ts+.5+mosaic.mosaic.xD,(y0+mosaic.yD)*ts+.5+mosaic.mosaic.yD,xs*ts,ys*ts);
  mosaic.mosaic.uilayer.restore();

}
//An interface to zero or more rectangular areas(rects)
//Each rect has an unique ID equal to its index in the array 'rects'. 
var RectManager = function(allowoverlap,boundingrect) {
  if(allowoverlap == undefined) allowoverlap=0;
  if(boundingrect == undefined) boundingrect={xD:0,yD:0,xS:0,yS:0};
  this.BoundingRect = boundingrect;
  return{
    rects: [],
    rectAt:
    function(xD,yD){
      for(var s in this.rects){ //ambiguous behavior if (xD,yD) is in the overlap between rectangles
        if(this.rects[s] == undefined) 
          continue;
        if(pointInRect({xD:xD,yD:yD},this.rects[s]) ){
            var r = this.rects[s];
            r.index = s;
            return r;       
          }
      }
      return -1;
    },
    removeRectAt:
    function(x0,y0){
      var i = this.rectAt(x0,y0).index;
      this.rects[i] = undefined;
       
    },
    //creates a new `rect` and adds it to the manager.  Returns the rectangle if successful, and 'undefined' if the rectangle would overlap another.
    makeRect:
    function(xD,yD,xS,yS){
      if(boundingrect.xS !=0 &&boundingrect.yS !=0)
        var nothing = 0;
        //check to see if rect is in bounds.  
        
      if(allowoverlap){
        rect = {xD:xD,yD:yD,xS:xS,yS:yS}
        this.rects.push(rect);
        return rect; 
      }

      for(i in this.rects){
        if(this.rects[i] == undefined){
          continue;
        }
        if(doesOverlap({xD:xD,yD:yD,xS:xS,yS:yS},this.rects[i]) ){
          return -1;
        }
      }
     var rect = {xD:xD,yD:yD,xS:xS,yS:yS};
     this.rects.push(rect);
     return rect;
     
    },

  }

}

var doesOverlap = function(rect1,rect2){
  var ax = Number(rect1.xD);
  var bx = Number(rect1.xD+rect1.xS);
  var ay = Number(rect1.yD);
  var by = Number(rect1.yD + rect1.yS);
  
  var cx = Number(rect2.xD);
  var dx = Number(rect2.xD + rect2.xS);
  var cy = Number(rect2.yD);
  var dy = Number(rect2.yD + rect2.yS);

  if(cx<bx && ax<dx && cy<by && ay<dy)
    return 1;
  return 0;
}

//returns 1 if `point` is inside of `rect`
var pointInRect = function(point,rect){
  if(  point.xD >= rect.xD && point.xD< rect.xD + rect.xS)
    if(point.yD >= rect.yD && point.yD< rect.yD + rect.yS)
      return 1;
  return 0


}



// src_xd,...,src_ys specify
// the origin and size of the rectangular area in the original image from which to pull pixels 
// the 'src_' prefix distinguishes them from the `x0` and `y0` args of `sprayImage`,
// which specify where the image should be painted on the mosaic or tile (in units of mosaic "pixels").
// 

var loaderCanvas = document.createElement('canvas');
var ImageLoader = function(image,src_xd,src_yd,src_xs,src_ys){
// This object holds an image in `canvas` (which is invisible--
// you can't get at an image's pixels unless it's in a canvas), and has the ability to spray that image onto a mosaic
// with `loadImage`, at the mosaic "pixel" positions specified by `x0` and `y0`.
// it populates `pixels` with a 2d array of `Color` objects
//`canvas` is a hidden canvas, which we'll use for pixel manipulation.

  //load the whole image by default.
  var canvas = loaderCanvas //loaderCanvas is the global canvas used by imageloader.
  if(image.width == undefined)//must be an id
    image = document.getElementById(image);
  if(src_xd == undefined){src_xd = 0;};
  if(src_yd == undefined){src_yd = 0;};
  if(src_xs == undefined){src_xs = image.width;};
  if(src_ys == undefined){src_ys = image.height;};
  canvas.width  = image.width;  //Resize the canvas so it can hold the image.
  canvas.height = image.height; 
  var ctx        = canvas.getContext('2d'); 
  ctx.drawImage(image,0,0);     //paint the image into our invisible canvas so can get at its pixels.
  var rawpixels = ctx.getImageData(0,0,canvas.width,canvas.height).data;
   // rawpixels = [r0,g0,b0,a0,r1,g1,b1,a1,...,rn,gn,bn,an] where n = w*h, as specified by W3C's ImageData object

  return {
    width:image.width,
    height:image.height,
    sprayImage:  
    function(mosaic,xd,yd,xs,ys,xOff,yOff){
      if(!xd) xd=0; //default image origin on the canvas is tile (0,0)
      if(!yd) yd=0;
      if(!xs) xs=image.width;
      if(!ys) ys=image.height;
      if(!xOff) xOff=0;
      if(!yOff) yOff=0;
      var color = {}; //
      var y = 0;
      var x = 0;
      var stopat = ys*image.width*4
      var elperrow = (xd+xs)*4
      //REDO NOW
      for(var i=(yd*xs+xd)*4;;i+=4){//I hate this routine, but it works.
        if(x == xs+xd){
          x  = 0;
          y += 1;
          i  = y*(image.width)*4;
          if(i>=stopat)
            break;
        }
        color.r = rawpixels[i];
        color.g = rawpixels[i+1];
        color.b = rawpixels[i+2];
        color.a = rawpixels[i+3];
        mosaic.paintCoords(x+xOff,y+yOff,color);
        x +=1;
      }
    },

  }
}


//formal definition of color "struct".  Need not be used to construct colors
var NewColor = function(r,g,b,a){
  color = {};
  color.r = r;
  color.g = g;
  color.b = b;
  color.a = a;
  return color;
}
var compareColors = function (color1,color2){
  if(color1.r == color2.r && color1.g == color2.g && color1.b == color2.b && color1.a == color2.a)
    return false;
  return true;
}

//A `Tile` is a managed area of mosaic space
var Tile = function(mosaic,x0,y0,xs,ys){
  //if(mosaic.tileSize*mosaic.xSize<*xs
  //|| mosaic.tileSize*mosaic.ySize<*ys
  //  ){
  //    return false; 
  //  }
  return{
    paintCoords:
    function(x,y,color){
      if(x>=this.xS || x<0 || y>=this.yS || y<0)
        return 1;
      mosaic.paintCoords(x+this.xOff,y+this.yOff,color)
      return false;
     
    },
    coordsOf:
    function(xpos,ypos){
      var coords = mosaic.coordsOf(xpos,ypos);
      coords[0] -= this.xOff;
      coords[1] -= this.yOff;
      if(coords[0] <0 || coords[1]<0)
        return [-1,-1];
      if(coords[0] > this.xSize || coords[1] > this.ySize)
        return [-1,-1];
      return coords;
    },
    setMosaic:
    function(newmosaic){
      mosaic = newmosaic;
    },
    xOff:x0,
    yOff:y0,
    xSize:xs,
    ySize:ys,
    mosaic:mosaic
  }
}

//A tile that remembers its contents
var MemTile = function(mosaic,x0,y0,xs,ys){
  var tile = Tile(mosaic,x0,y0,xs,ys);
  if(!tile)
    return false;
  var pixels = [];
  for(var y=0;y<ys;y++){
    pixels[y] = [];
  }

  tile.parentPaintCoords = tile.paintCoords;
	var paintCoords =
	function(x,y,color){
	if(tile.parentPaintCoords(x,y,color) )
	return 1;
	this.pixels[y][x] = {r:color.r,g:color.g,b:color.b,a:color.a}
	};
	var redraw= 
	function(){
	var color;
	for(row in pixels)
	for(col in row)
	  this.parentPaintCoords(col,row,pixels[row][col]);
	  
	};
  var sprayImage = function(mosaic,x0,y0,xs,ys,xOff,yOff){
    if(!xs)  xs=this.xSize;
    if(!ys)  ys=this.ySize;
    if(!xOff)xOff=0;
    if(!yOff)yOff=0;
    var y=0; var x=0;
    for(y=0;y<ys;y++){
      for(x=0;x<xs;x++){
        mosaic.paintCoords(x+xOff,y+yOff,this.pixels[y+y0][x+x0]);
      }
    }
  }
  tile.sprayImage = sprayImage;
  tile.paintCoords = paintCoords;
  tile.pixels = pixels;
  return tile;
}

//keeps track of tiles in the rectangular mosaic area defined by its arguments.
//the rectmanager's 'rect' object has here been augmented with a `Tile`
var TileManager = function(mosaic,tiletype,allowoverlap){
  if(!allowoverlap)allowoverlap=false;
  if(tiletype==undefined)
    tiletype = Tile;
  rectmanager = RectManager(allowoverlap);
  return {
    makeTile: 
    function(tx0,ty0,txs,tys,name){
        
      r = rectmanager.makeRect(tx0,ty0,txs,tys);
      if(r==-1)
        return -1;
      var t = tiletype(mosaic,tx0,ty0,txs,tys);
      r.tile = t;
      if(name)
        this.namedtiles[name] = r.tile;
      return r.tile;
    },

    tileAt: 
    function(tx0,ty0){
      return rectmanager.rectAt(tx0,ty0).tile;
    },
    tileNamed:
    function(name){
      return this.namedtiles[name];
    },

    removeTileAt:
    function(tx0,ty0){
      rectmanager.removeRectAt(tx0,ty0);
    },
    setMosaic:
    function(mymosaic){
      for(i in rectmanager.rects){
        tile = rectmanager.rects[i].tile
        if(tile)
          tile.setMosaic(mymosaic);
      }
    },
    namedtiles:{},
    rectsmanager:rectmanager
  }
}

//A click-and-drag selector that snaps to the tiles of its mosaic
//when asked to.
//This object is meant to be instantiated once per click-and-drag,
//with its snap (which returns a rect
//

var DragBox = function(mosaic,initial_xpos,initial_ypos){
  return {
    track:
    function(xpos,ypos){
      mosaic.uilayer2.save();
      mosaic.uilayer2.clearRect(mosaic.xD,mosaic.yD,mosaic.xSize,mosaic.ySize);      
      mosaic.uilayer2.lineWidth = 1;
      mosaic.uilayer2.strokeRect(
      initial_xpos-.5,         //x0
      initial_ypos-.5,         //y0
      xpos  - initial_xpos ,   //xs
      ypos - initial_ypos      //ys
      );
      mosaic.uilayer2.restore();
    },
    snap:
    function(xpos,ypos){
      var coords = this.coordsOf(xpos,ypos);
      xpos = coords[0]*mosaic.tileSize+mosaic.xD;
      ypos = coords[1]*mosaic.tileSize+mosaic.yD;
      coords = this.coordsOf(initial_xpos,initial_ypos);
      initial_xpos = coords[0]*mosaic.tileSize+mosaic.xD;
      initial_ypos = coords[1]*mosaic.tileSize+mosaic.yD;
      this.track(xpos,ypos);

      //done with display-- now calculate the proper `rect` to return,
      //which must be  of form x0,y0,xS,yS.
      var finalcoords = mosaic.coordsOf(initial_xpos-1,initial_ypos-1)
      var xS = (xpos-initial_xpos)/mosaic.tileSize;
      var yS = (ypos-initial_ypos)/mosaic.tileSize;
      var x0 = xS>0 ? finalcoords[0] : finalcoords[0]+xS;
      var y0 = yS>0 ? finalcoords[1] : finalcoords[1]+yS;
      return {x0:x0,
              y0:y0,
              xS:Math.abs(xS),
              yS:Math.abs(yS)
              };
    },
    coordsOf: //alternate implementation of mosaic's `coordsOf` that rounds
              //instead of truncating-.
    function(xpos,ypos){
      var x = Math.round( (xpos - mosaic.xD)/mosaic.tileSize); 
      var y = Math.round( (ypos - mosaic.yD)/mosaic.tileSize);
      return [x,y]; 
      
    },
  }
}
/*
var Sprited = function(prefs){
  var mos  = Mosaic("canvases",{"tileSize":3});
  var mos2 = Mosaic("canvases",{"tileSize":4});
  selector = Selector(mos);
  imageloader = ImageLoader(document.getElementById("megaman"));
  imageloader.sprayImage(mos2,0,0,200,100,5,5)
  var tileman = TileManager(mos,MemTile);
  tileman.makeTile(0,0,200,200);
  return {
    click:
    function(xpos,ypos){
      var coords = mos.coordsOf(xpos,ypos)
      //if(-1 ==selector.boxAt(coords[0],coords[1]) )
      //  return false;
      var tile = tileman.tileAt(coords[0],coords[1]);
      var coords = tile.coordsOf(xpos,ypos);
      tile.paintCoords(coords[0],coords[1],{r:"128",g:"128",b:"128",a:"128"});
    },
    mos: mos,
    mos2: mos2
  }
}

var draw = function(){
  app = Sprited();
  document.onmousedown =function(e){
    dragbox = DragBox(app.mos2,e.pageX-1,e.pageY-1);      
    document.onmousemove = function(e){
      //app.click(e.pageX-1,e.pageY-1);
      dragbox.track(e.pageX-1,e.pageY-1);
    }
  }
  document.onmouseup = function(e){
    result = dragbox.snap(e.pageX-1,e.pageY-1);
    alert("x0:"+result.x0+",y0:"+result.y0+",xS:"+result.xS+",yS:"+result.yS)
    document.onmousemove = function(e){
      return false;
    }
  }

}
*/
